import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.json.simple.JSONObject;

class VisualizationBuilder {
  
    float scale = 0.5;
    // The coordinates of the surface(district) 
    int x_district = 700;
    int y_surface = 670;
    int z_district = -150;
    
    // Set the district's size as a constant, value to change
    int w_district = 900;
    int h_district = 6;
    int l_district = 500;
    float height_building = 50;
    
    // Temp values
    float w_temp = 0;
    float l_temp = 0;
    float x_temp = 0;
    float z_temp = 0;
    
    float old_b_width = 0;
    float old_b_length = 0;
    
    float minNum = 0;
    
    // Initial coordinates, the center of district
    float x_init = 325;
    float z_init = -300;
    
    // ArrayList
    List<Float> xList = new ArrayList<Float>();
    List<Float> zList = new ArrayList<Float>();
    List<Float> wList = new ArrayList<Float>();
    List<Float> lList = new ArrayList<Float>();
    
    // Constructer
    VisualizationBuilder() {
    }
    
    void drawBuilding(int area, List<JSONObject> loj, float dotSize) {
      
        int count = 0;
        
        // if count reachs the maximum of 20, then give up on this building (due to no available space)
        while(count < 20) {
                
           int y = y_surface;
           
           // Generate the components of a building  
           // Call calculateWidthLength to generate the width and length of the building
           calculateWidthLength(area);
           float w = w_temp;
           float l = l_temp;
              
           // Call calculateMinDistance to generate the new coordinate of the building
           calculateMinDistance(w,l,x_init,z_init);
           float x = x_temp;
           float z = z_temp;
              
           // check overlay with buildings, checkOverlay(w,l,x,z);
           // If the new building doesn't overlay with any then draw it.
           // if yes then, count ++
           if(!checkOverlayAnyBuildings(w,l,x,z)) {
            
              // For each floor
              for(int i = 0; i < min(12, loj.size()); i++) {
                
                 // draw the floor
                 drawfloor(w, height_building, l, x, y - h_district/2 - i*height_building, z); 
                 
                 // draw the dots in the floor  
                 int numOfIfs = (Integer) loj.get(i).get("If Count");
                 int numOfLoops = (Integer) loj.get(i).get("Loop Count");
                 int numOfOthers = (Integer) loj.get(i).get("Other Count");
                 float totalFlow = (float) numOfIfs + (float) numOfLoops + (float) numOfOthers;
                 
                 float new_x_low = 0;
                 float ratOfDots = 0;
                 float random_x_low = 0;
                 float random_x_high = 0;
                 int numOfDots = (Integer) loj.get(i).get("Absolute Chars");
                 
                 //Separates the dots into blocks and colors them according to 
                 //the proportions of the control flow statements
                 for( int j = 0; j < 3; j++ ) {
                   
                   if( j == 0 ) {
                     float ratOfIfs = numOfIfs/totalFlow;
                     random_x_low = x - w/2 + dotSize*4;
                     random_x_high = random_x_low + (w - dotSize*8) * ratOfIfs; 
                     ratOfDots = (float)numOfDots * ratOfIfs;
                     new_x_low = random_x_high;
                   }
                   else if( j == 1 ) {
                     float ratOfLoops = numOfLoops/totalFlow;
                     random_x_low = new_x_low;
                     random_x_high = random_x_low + (w - dotSize*8) * ratOfLoops; 
                     ratOfDots = (float)numOfDots * ratOfLoops;
                     new_x_low = random_x_high;
                   }
                   else if( j == 2 ) {
                     float ratOfOthers = numOfOthers/totalFlow;
                     random_x_low = new_x_low;
                     random_x_high = random_x_low + (w - dotSize*8) * ratOfOthers; 
                     ratOfDots = (float)numOfDots * ratOfOthers;
                   }
                   
                   float random_y_low = y - h_district/2 - height_building*(i+1) + dotSize*4;
                   float random_y_high = y - h_district/2 - height_building*i - dotSize*4;
                   float random_z_low = z - l/2 + dotSize*4;
                   float random_z_high = z + l/2 - dotSize*4;
                   
                   int numOfDotsDivide = (Integer) Math.round(ratOfDots) / 1;
                   int relativeChangeOfDots = (Integer) loj.get(i).get("Relative Chars");
                   drawRandomDot(dotSize,numOfDotsDivide,relativeChangeOfDots,
                                 random_x_high,
                                 random_x_low,
                                 random_y_high,
                                 random_y_low,
                                 random_z_high,
                                 random_z_low,
                                 j);
                 }
              }
              
              // set the width and length of current building to the old values of width and length and also the x_init and z_init
              old_b_width = w;
              old_b_length = l;
              
              x_init = x;
              z_init = z;
              
              xList.add(x);
              zList.add(z);
              wList.add(w);
              lList.add(l);        
              return;
           } 
           else {
              count++;
           }
        }      
        return;
    }
    
    void drawDistrict(color c) {
      pushMatrix(); 
      fill(c);
      translate(x_district, y_surface, z_district);
      stroke(0);
      // The size of the district is constant!
      box(w_district,h_district,l_district);
      popMatrix();
    }
    
    void drawfloor(float w, float h, float l, float x, float y, float z) {
      y = y - h/2;   
      pushMatrix();
      translate(x,y,z);
      noFill();
      stroke(80,80,80);
      box(w,h,l);
      popMatrix();
      
    }
    
    void drawDot(float size, float x, float y, float z) {
       pushMatrix();
       translate(x,y,z);
       sphere(size);
       popMatrix();
    }
    
    void drawRandomDot(float size, int numDots, int relativeChange, float x_high, float x_low, float y_high, float y_low, float z_high, float z_low, int flow) {
     
      //Gets the brightness level from relative change
      color bgtOfDots = brightDetermin(relativeChange);
      colorMode(HSB, 360, 100, 100);
      if(flow == 0) {
        stroke(357, 99, bgtOfDots);
      }
      else if(flow == 1) {
        stroke(123, 99, bgtOfDots);
      }
      else if(flow == 2) {
        stroke(226, 99, bgtOfDots);
      }
      for (int i = 0; i < numDots; i++) {
        float x = random(x_low,x_high);
        float y = random(y_low,y_high);
        float z = random(z_low,z_high);
        drawDot(size, x, y, z);  
      }
      colorMode(RGB, 255);
    } 
    
    void calculateMinDistance(float wNew, float lNew, float x, float z) {
      
        float newX = -100000;
        float newZ = -100000;
        float w = old_b_width;
        float l = old_b_length;
        
        boolean b = true;
        
        while(b) {
          
          // Generate a new random num b/w 0 and 1  
          float randomNum = random(0,1);
        
          // Right now, higher chance to put the next building on the right/down side.
          if(0 <= randomNum & randomNum < 0.25) {
            // Pick x, and postive AREA
            newX = x+0.5*w+minNum+0.5*wNew;
            newZ = random(z-0.5*l-minNum-0.5*lNew,z+0.5*l+minNum+0.5*lNew);
          }
          if(0.25 <= randomNum & randomNum < 0.5) {
            // Pick x, and negative AREA
            newX = x-0.5*w-minNum-0.5*wNew;
            newZ = random(z-0.5*l-minNum-0.5*lNew,z+0.5*l+minNum+0.5*lNew);
          }
          if(0.5 <= randomNum & randomNum < 0.75) {
            // Pick z, and positive AREA
            newX = random(x-0.5*w-minNum-0.5*wNew,x+0.5*w+minNum+0.5*wNew);
            newZ = z+0.5*l+minNum+0.5*lNew;
          }
          if(0.75 <= randomNum & randomNum <= 1) {
            // Pick z, and negative AREA
            newX = random(x-0.5*w-minNum-0.5*wNew,x+0.5*w+minNum+0.5*wNew);
            newZ = z-0.5*l-minNum-0.5*lNew;
          }
          
          // Set the boolean output from checkOutOfBound to b
          // If b is false, then re-generate newX and newZ
          // else set newX and newZ into the temp values
          b = checkOutOfBound(wNew,lNew,newX,newZ);
        }
      
        // Generate a new min distance value for the next building,
        // random from 40 to 60.
        minNum = random(40,60);
        x_temp = newX;
        z_temp = newZ; 
    }
    
    // Check if the new building overlays with any others
    // True if it DOES overlay with any others.
    boolean checkOverlayAnyBuildings(float w, float l, float x, float z){
      
      boolean b = false;
      
      // Base Case, if two lists are empty then no chance to overlay;
      if(xList.size() == 0 && zList.size() == 0 && wList.size() == 0 && lList.size() == 0) {
        return false;
      } else {
        // Check if it overlay with anyone in the lists
        for(int i = 0; i < xList.size(); i++) {
           // check if the new building overlay with one element in the list.
           b = b || touchOrNot(w,l,x,z,wList.get(i),lList.get(i),xList.get(i),zList.get(i));        
        }      
        return b;
      }
    }

    // Check if "two buildings" touch with each other, if the distance between them is 
    // less than 20, they will be considered as touchinging each other.
    // True if they touch
    boolean touchOrNot(float w, float l, float x, float z, float w2, float l2, float x2, float z2) {
      
        float minNum = 20;
        return (x-0.5*w-0.5*w2-minNum < x2 && x2 < x+0.5*w+0.5*w2+minNum) && 
               (z-0.5*l-0.5*l2-minNum < z2 && z2 < z+0.5*l+0.5*l2+minNum);
               
    }
    
    // Check if the building is out of boundary,
    // True if it is out of bound
    boolean checkOutOfBound(float w, float l, float x, float z){
      
        float x_bound_left = x_district-0.5*w_district+0.5*w;
        float x_bound_right = x_district+0.5*w_district-0.5*w;
        float z_bound_up = z_district-0.5*l_district+0.5*l;
        float z_bound_down = z_district+0.5*l_district-0.5*l;

        return !(x >= x_bound_left && x <= x_bound_right && z >= z_bound_up && z <= z_bound_down);
      
    }
    
    //Takes the total area and calculates the width and length
    void calculateWidthLength(int area) {
      
      // scale
      float areaF = area*scale;
      float minArea = 2500*scale;
      float maxArea = 30000*scale;
      areaF = min(max(areaF*3,minArea),maxArea);
      
      float x_high = sqrt(areaF)*1.25;
      float x_low =  sqrt(areaF)*0.8;
      float x_random = random(x_low,x_high);
      w_temp = x_random;
      l_temp = areaF/x_random;
      
    }
    
    //Determines the brightness level
    color brightDetermin(Integer i) { 
        if(i >= 80) return 99;
        if(i >= 60 & i < 80) return 79;
        if(i >= 40 & i < 60) return 59;
        if(i >= 5 & i < 40) return 39;
        if(i >= 0   & i < 5) return 19;
        if(i >= -20 & i < 0) return 39;
        if(i >= -40 & i < -20) return 59;
        if(i >= -60 & i < -40) return 79;
        if(i < -60) return 99;
        else return 99;
     }  
}

